Skip to content

Conversation

@aborgna-q
Copy link
Collaborator

Includes an idea for simplifying the protocol's _apply/_apply_inline from #2697 by providing a helper function instead (859c811).

@aborgna-q aborgna-q force-pushed the ab/composed-pass-result branch from 68df8b0 to 567c58d Compare November 20, 2025 14:44
@aborgna-q aborgna-q force-pushed the ab/composed-pass-result branch from 567c58d to d79a031 Compare November 20, 2025 14:47
@codecov
Copy link

codecov bot commented Nov 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.54%. Comparing base (dbf8c8e) to head (7cf550d).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2703      +/-   ##
==========================================
+ Coverage   83.49%   83.54%   +0.05%     
==========================================
  Files         266      266              
  Lines       51744    51667      -77     
  Branches    47180    47086      -94     
==========================================
- Hits        43205    43167      -38     
+ Misses       6161     6124      -37     
+ Partials     2378     2376       -2     
Flag Coverage Δ
python 91.66% <100.00%> (+0.66%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@CalMacCQ CalMacCQ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks nice, thanks. I find this more intuntive than having _apply and _apply_inplace methods (at least one of which must be overriden when the protocol is implemented.

One question for clarification and also one nit.

class ComposablePass(Protocol):
"""A Protocol which represents a composable Hugr transformation."""

def __call__(self, hugr: Hugr, *, inplace: bool = True) -> Hugr:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have the impl_pass_run function as a helper for implementing ComposablePass.run where is the __call__ method actually used in the pass implementation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a suggestion from Seyon. Most usecases just need the Hugr after the pass, so we provide a simple call method.

When we actually need to inspect the result/pass output we can use the other call.

@aborgna-q aborgna-q force-pushed the ab/composed-pass-result branch from fce9705 to 07caa46 Compare November 24, 2025 10:08
@aborgna-q aborgna-q marked this pull request as ready for review November 24, 2025 10:09
@aborgna-q aborgna-q requested a review from a team as a code owner November 24, 2025 10:09
@aborgna-q aborgna-q requested review from cqc-alec and removed request for a team November 24, 2025 10:09
hugr=hugr,
inplace=inplace,
inplace_call=apply_inplace,
inplace_call=apply,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inplace_call and copy_call are the same function here, is that right? Either of them will capture local variable inplace: bool = True specified in the call to run.

Ah ok. I think the one that's going to be used will have the correct semantics, and the callback that isn't going to be used....had better not be, it will not work correctly

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added explicit flag overrides

:attr hugr: The transformed Hugr.
:attr original_dirty: Whether the original HUGR was modified by the pass.
:attr modified: Whether the pass made changes to the HUGR.
Copy link
Contributor

@acl-cqc acl-cqc Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so if inplace = True,

  • original_dirty == modified, and the transformed Hugr is the input Hugr

whereas if inplace == False,

  • original_dirty should always be False
  • modified indicates whether the output Hugr == the input
  • (probably but not necessarily) the output never is the input. (We might want to allow the latter if modified is False)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modified indicates whether the output Hugr == the input; but (probably, not necessarily) the output never is the input. (We might want to allow the latter if modified is False)

Conditionally aliasing the output is a bug waiting to happen. If we say the output is a copying the object then we should always do that.
Otherwise we may start modifying the result and unintentionally changing the original too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Ok, so - if inplace:

  • original_dirty == modified
  • output Hugr is input Hugr

if not inplace:

  • original_dirty == False? (Right? We don't allow it to be partially invalidated or anything)
  • (output Hugr is input Hugr) is always False
  • modified indicates whether the output Hugr == the input

Base automatically changed from cm/overwrite_hugr_method to main November 24, 2025 10:31


def impl_pass_call(
def impl_pass_run(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me this name feels too "internal". I know it's inside an underscored module but when we stabilize it we may want a better name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed it to "implement_pass_run".
Not the best either, but hopefully a bit less internal-sounding

modify the program.
:attr hugr: The transformed Hugr.
:attr original_dirty: Whether the original HUGR was modified by the pass.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "dirty" the right word here? The meaning of original_dirty and modified look very similar. I don't actually see the point of the original_dirty flag. Presumably the user either knows or doesn't care whether the pass was called in-place.

Copy link
Collaborator Author

@aborgna-q aborgna-q Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced original_dirty with just an inplace flag.
I think it's nice to have a flag assuring you whether the hugr got copied or not, but it's not super important.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. We also rule out any optimization that wants to destroy the input Hugr in the process of constructing an all-new output Hugr. I mean, ruling that out might be good ;)

hugr: Hugr
original_dirty: bool = False
modified: bool = False
results: list[tuple[PassName, Any]] = field(default_factory=list)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why we include the pass name here?

Copy link
Collaborator Author

@aborgna-q aborgna-q Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly for debuggability, otherwise ComposedPass.run would return a List[Any] of arbitrary payloads without much indication of what came from where.

The result may also be serialized, so the original ComposedPass and its list of passes may be lost.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought including the pass name was quite a nice solution!

else:
self.passes.append(composable_pass)

def run(self, hugr: Hugr, *, inplace: bool = True) -> PassResult:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think you can just do:

def run(self, hugr: Hugr, *, inplace: bool = True) -> PassResult:
  pass_result = PassResult(hugr=hugr)
  for pass_ in self.passes:
    new_result = pass_.run(pass_result.hugr, inplace=inplace)
    pass_result = pass_result.then(new_result)
  return pass_result

Because pass_.run always returns a PassResult containing the result hugr; we don't care whether that's the input hugr or a fresh one.

Copy link
Collaborator Author

@aborgna-q aborgna-q Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's already the case, or I'm missing something?

@aborgna-q aborgna-q added this pull request to the merge queue Nov 24, 2025
Merged via the queue into main with commit b8df28e Nov 24, 2025
29 checks passed
@aborgna-q aborgna-q deleted the ab/composed-pass-result branch November 24, 2025 12:15
aborgna-q added a commit that referenced this pull request Nov 24, 2025
Includes an idea for simplifying the protocol's `_apply`/`_apply_inline`
from #2697 by providing a helper function instead
(859c811).

---------

Co-authored-by: Callum Macpherson <[email protected]>
Co-authored-by: Callum Macpherson <[email protected]>
Co-authored-by: Alan Lawrence <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants